package org.ghost.wechat.platform.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.ghost.wechat.platform.common.constant.CacheKeyConstant;
import org.ghost.wechat.platform.common.enumeration.CommonBusinessErrorCodeEnum;
import org.ghost.wechat.platform.common.enumeration.SystemErrorCodeEnum;
import org.ghost.wechat.platform.common.http.HttpClientFactory;
import org.ghost.wechat.platform.common.redis.RedisCacheUtil;
import org.ghost.wechat.platform.dao.IWeChatBaseDAO;
import org.ghost.wechat.platform.dto.wechat.base.WeChatAccessTokenRspDTO;
import org.ghost.wechat.platform.entity.MediaPlatform;
import org.ghost.wechat.platform.enumeration.WechatGrantTypeEnum;
import org.ghost.wechat.platform.service.IMediaPlatformService;
import org.ghost.wechat.platform.util.DateUtil;
import org.ghost.wechat.platform.util.ExceptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author 01
 */
public abstract class AbstractServiceImpl {
    private static final Logger logger = LoggerFactory.getLogger(AbstractServiceImpl.class);

    /**
     * access_token刷新次数阈值,超过了预警
     */
    private static final int ACCESS_TOKEN_REFRESH_THRESHOLD = 1000;

    protected IWeChatBaseDAO weChatBaseDAO = HttpClientFactory.getBean(IWeChatBaseDAO.class);
    @Autowired
    private IMediaPlatformService mediaPlatformService;
    @Autowired
    protected RedisCacheUtil redisCacheUtil;

    /**
     * 刷新access_token(应该统一管理)
     *
     * @param appId
     * @param secret
     * @param accessToken
     * @return
     */
    public synchronized String refresh(String appId, String secret, String accessToken) {
        if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(secret)) {
            String key = MessageFormat.format(CacheKeyConstant.WX_ACCESS_TOKEN, appId);
            String lockKey = MessageFormat.format(CacheKeyConstant.WX_ACCESS_TOKEN_LOCK, appId);

            if (StringUtils.isNotBlank(accessToken)) {
                String cacheToken = this.redisCacheUtil.get(key, String.class);
                if (!Objects.equals(accessToken, cacheToken)) {
                    return cacheToken;
                }
            }

            if (redisCacheUtil.tryLock(lockKey, 1, TimeUnit.MINUTES)) {
                try {
                    WeChatAccessTokenRspDTO accessTokenRspDTO = this.weChatBaseDAO.getAccessToken(appId, secret, WechatGrantTypeEnum.CLIENT_CREDENTIAL.getCode());
                    this.logRefreshTimes(appId);
                    if (accessTokenRspDTO != null && StringUtils.isNotBlank(accessTokenRspDTO.getAccessToken()) && accessTokenRspDTO.getExpiresIn() != null) {
                        //加入缓存
                        this.redisCacheUtil.set(key, accessTokenRspDTO.getAccessToken(), accessTokenRspDTO.getExpiresIn() - 5);
                        return accessTokenRspDTO.getAccessToken();
                    } else {
                        this.redisCacheUtil.unlock(lockKey);
                        String errorMsg = accessTokenRspDTO != null ? accessTokenRspDTO.getErrCode() + accessTokenRspDTO.getErrMsg() : SystemErrorCodeEnum.SERVICE_NO_RESPONSE_ERROR.getMessage();
                        logger.error("*****WeChatWebServiceImpl.getAccessToken出现错误:" + errorMsg);
                    }
                } catch (Exception e) {
                    //设置失败,让其他节点获取锁权限继续设置
                    this.redisCacheUtil.unlock(lockKey);
                    logger.error("*****WeChatWebServiceImpl.refreshAccessToken出现异常,释放锁权限:appId={},appSecret={},msg={},error={}", appId, secret, e.getMessage(), e);
                }
                //finally {
                //设置成功,继续保持锁定,让锁获取失败读取缓存数据
                //redisCacheUtil.unlock(lockKey);
                //}
            } else {
                return this.redisCacheUtil.get(key, String.class);
            }
        }
        return null;
    }

    private void logRefreshTimes(String appId) {
        CompletableFuture.runAsync(() -> {
            String key = MessageFormat.format(CacheKeyConstant.WX_ACCESS_TOKEN_REFRESH_COUNT, appId, DateUtil.dateToStr(new Date(), DateUtil.DATE_FORMAT12));
            Long value = this.redisCacheUtil.incr(key);
            LocalDateTime now = LocalDateTime.now();
            this.redisCacheUtil.expireAt(key, Date.from(LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()));
            if (value != null && value > ACCESS_TOKEN_REFRESH_THRESHOLD) {
                //发邮件等通知
                logger.warn("*****WeChatWebServiceImpl.refreshAccessToken超过了设定的刷新次数阈值:appId={},threshold={}", appId, ACCESS_TOKEN_REFRESH_THRESHOLD);
            }
        });
    }

    /**
     * 优先从缓存中取access_token
     *
     * @param token token
     * @return
     */
    public String getAccessToken(String token) {
        if (StringUtils.isNotBlank(token)) {
            return this.getAccessToken(token, false, null);
        }
        return null;
    }

    /**
     * 根据参数获取access_token
     *
     * @param token       token
     * @param force       是否强制刷新
     * @param accessToken 上次请求失败的access_token
     * @return
     */
    public String getAccessToken(String token, boolean force, String accessToken) {
        if (StringUtils.isNotBlank(token)) {
            MediaPlatform mediaPlatform = this.getWeChatConfig(token);
            if (force) {
                return this.refresh(mediaPlatform.getAppId(), mediaPlatform.getAppSecret(), accessToken);
            } else {
                String key = MessageFormat.format(CacheKeyConstant.WX_ACCESS_TOKEN, mediaPlatform.getAppId());
                String value = this.redisCacheUtil.get(key, String.class);
                if (StringUtils.isNotBlank(value)) {
                    return value;
                } else {
                    return this.refresh(mediaPlatform.getAppId(), mediaPlatform.getAppSecret(), null);
                }
            }
        }
        return null;
    }

    public MediaPlatform getWeChatConfig(String token) {
        MediaPlatform result;
        if (StringUtils.isNotBlank(token)) {
            result = this.mediaPlatformService.getOne(token, "app_id", "app_secret", "app_token", "aes_key");
        } else {
            result = null;
        }
        if (result == null) {
            ExceptionUtil.throwRuntimeException(CommonBusinessErrorCodeEnum.TOKEN_ERROR, "AbstractServiceImpl.getWeChatConfig", logger);
        }
        return result;
    }
}
